/*
 *---------------------------------------------------------------------------
 *
 * Copyright (c) 2020, 2021 Analog Devices, Inc. All Rights Reserved.
 * This software is proprietary to Analog Devices, Inc.
 * and its licensors.By using this software you agree to the terms of the
 * associated Analog Devices Software License Agreement.
 *
 *---------------------------------------------------------------------------
 */

#include "main.h"

/* Number of frames to transmit. If 0, it runs continuously. */
#define FRAME_COUNT         (10000)
/* Frame size in bytes, applies to each of the transmitted frames. */
/* The value should be between 64 and 1518. */
#define FRAME_SIZE          (1518)

/* Number of buffer descriptors to use for both Tx and Rx in this example */
#define BUFF_DESC_COUNT     (6)

#define ADIN1110_INIT_ITER  (5)

/* Example configuration */
uint8_t devMem[ADIN1110_DEVICE_SIZE];

adin1110_DriverConfig_t drvConfig = {
    .pDevMem    = (void *)devMem,
    .devMemSize = sizeof(devMem),
    .fcsCheckEn = false,
};

/* Forward declarations */
void    printStats     (adin1110_DeviceHandle_t hDevice);

uint32_t               txIdx = 0;
volatile uint32_t      rxIdx = 0;
volatile uint32_t      expectedTxIdx;
volatile uint32_t      expectedRxIdx;
volatile uint32_t      errorTxIdx;
volatile uint32_t      errorRxIdx;

HAL_ALIGNED_PRAGMA(4)
static uint8_t rxBuf[BUFF_DESC_COUNT][MAX_FRAME_BUF_SIZE] HAL_ALIGNED_ATTRIBUTE(4);

HAL_ALIGNED_PRAGMA(4)
static uint8_t txBuf[BUFF_DESC_COUNT][MAX_FRAME_BUF_SIZE] HAL_ALIGNED_ATTRIBUTE(4);

bool txBufAvailable[BUFF_DESC_COUNT];

static void txCallback(void *pCBParam, uint32_t Event, void *pArg)
{
    adi_eth_BufDesc_t       *pTxBufDesc;
    uint32_t                idx;

    pTxBufDesc = (adi_eth_BufDesc_t *)pArg;
    txIdx++;

    memcpy(&idx, &pTxBufDesc->pBuf[14], 4);

    /* Log a Tx index error if current frame index does not match expected value */
    if (idx != expectedTxIdx)
    {
        errorTxIdx++;
    }
    /* Next frame is expected to have an index incremented by 1 from the current frame index. */
    expectedTxIdx = idx + 1;

    /* Buffer has been written to the ADIN1110 Tx FIFO, we mark it available */
    /* to re-submit to the Tx queue with updated contents. */
    for (uint32_t i = 0; i < BUFF_DESC_COUNT; i++)
    {
        if (&txBuf[i][0] == pTxBufDesc->pBuf)
        {
            txBufAvailable[i] = true;
            break;
        }
    }
}

static void rxCallback(void *pCBParam, uint32_t Event, void *pArg)
{
    adin1110_DeviceHandle_t hDevice = (adin1110_DeviceHandle_t)pCBParam;
    adi_eth_BufDesc_t       *pRxBufDesc;
    uint32_t                idx;

    pRxBufDesc = (adi_eth_BufDesc_t *)pArg;
    rxIdx++;

    memcpy(&idx, &pRxBufDesc->pBuf[14], 4);

    /* Log an Rx index error if current frame index does not match expected value */
    if (idx != expectedRxIdx)
    {
        errorRxIdx++;
    }
    /* Next frame is expected to have an index incremented by 1 from the current frame index. */
    expectedRxIdx = idx + 1;

    /* Since we're not doing anything with the Rx buffer in this example, */
    /* we are re-submitting it to the queue. */
    adin1110_SubmitRxBuffer(hDevice, pRxBufDesc);
}

void cbLinkChange(void *pCBParam, uint32_t Event, void *pArg)
{
    adi_eth_LinkStatus_e    linkStatus;

    linkStatus = *(adi_eth_LinkStatus_e *)pArg;

    (void)linkStatus;
}

int main(void)
{
    adi_eth_Result_e        result;
    uint32_t                error;
    adin1110_DeviceStruct_t dev;
    adin1110_DeviceHandle_t hDevice = &dev;
    uint32_t                heartbeatCheckTime = 0;

    /****** System Init *****/
    error = BSP_InitSystem();
    DEBUG_RESULT("BSP_InitSystem", error, 0);

    /* Hardware reset of the ADIN1110 using a host GPIO */
    BSP_HWReset(true);

    /****** Driver Init *****/
    for (uint32_t i = 0; i < ADIN1110_INIT_ITER; i++)
    {
        result = adin1110_Init(hDevice, &drvConfig);
        if (result == ADI_ETH_SUCCESS)
        {
            break;
        }
    }
    DEBUG_RESULT("No MACPHY device found", result, ADI_ETH_SUCCESS);

    result = adin1110_AddAddressFilter(hDevice, &macAddr[0][0], NULL, 0);
    DEBUG_RESULT("adin1110_AddAddressFilter", result, ADI_ETH_SUCCESS);

    result = adin1110_AddAddressFilter(hDevice, &macAddr[1][0], NULL, 0);
    DEBUG_RESULT("adin1110_AddAddressFilter", result, ADI_ETH_SUCCESS);

    result = adin1110_SyncConfig(hDevice);
    DEBUG_RESULT("adin1110_SyncConfig", result, ADI_ETH_SUCCESS);

    result = adin1110_RegisterCallback(hDevice, cbLinkChange, ADI_MAC_EVT_LINK_CHANGE);
    DEBUG_RESULT("adin1110_RegisterCallback (ADI_MAC_EVT_LINK_CHANGE)", result, ADI_ETH_SUCCESS);

    adi_eth_BufDesc_t       rxBufDesc[BUFF_DESC_COUNT];
    uint32_t                txBufDescIdx = 0;
    adi_eth_BufDesc_t       txBufDesc[BUFF_DESC_COUNT];
    uint32_t                testFramesIdx = 0;
    adi_eth_LinkStatus_e    linkStatus;
    uint32_t                frameIdx;
    uint32_t                heartbeatTicks;

    /* Prepare Tx/Rx buffers */
    for (uint32_t i = 0; i < BUFF_DESC_COUNT; i++)
    {
        memcpy(&txBuf[i], &testFrames[i % 2][0], FRAME_SIZE);
        txBufAvailable[i] = true;

        rxBufDesc[i].pBuf = &rxBuf[i][0];
        rxBufDesc[i].bufSize = MAX_FRAME_BUF_SIZE;
        rxBufDesc[i].cbFunc = rxCallback;

        result = adin1110_SubmitRxBuffer(hDevice, &rxBufDesc[i]);
    }

    result = adin1110_Enable(hDevice);
    DEBUG_RESULT("Device enable error", result, ADI_ETH_SUCCESS);

    /* Wait for link to be established */
    do
    {
        result = adin1110_GetLinkStatus(hDevice, &linkStatus);
        DEBUG_RESULT("adin1110_GetLinkStatus", result, ADI_ETH_SUCCESS);
    } while (linkStatus != ADI_ETH_LINK_STATUS_UP);

    /* Reset buffer descriptor index */
    expectedRxIdx = 0;
    expectedTxIdx = 0;

    errorTxIdx = 0;
    errorRxIdx = 0;

    frameIdx = 0;
    heartbeatTicks = 0;
    /* if FRAME_COUNT == 0, run in an infinite loop. */
    while (!FRAME_COUNT || (txIdx < FRAME_COUNT))
    {
        uint32_t now  = BSP_SysNow();

        /* Heartbeat pulse approximately once every 250ms. */
        if (now - heartbeatCheckTime >= 250)
        {
            heartbeatCheckTime = now;
            BSP_HeartBeat();
            heartbeatTicks++;
        }

        if (txBufAvailable[testFramesIdx])
        {
            txBufDesc[txBufDescIdx].pBuf = &txBuf[testFramesIdx][0];
            txBufDesc[txBufDescIdx].trxSize = FRAME_SIZE;
            txBufDesc[txBufDescIdx].bufSize = MAX_FRAME_BUF_SIZE;
            txBufDesc[txBufDescIdx].egressCapt = ADI_MAC_EGRESS_CAPTURE_NONE;
            txBufDesc[txBufDescIdx].cbFunc = txCallback;

            /* Update the frame contents to include the frame index */
            uint32_t *p = (uint32_t *)&txBufDesc[txBufDescIdx].pBuf[14];
            *p = frameIdx;

            txBufAvailable[testFramesIdx] = false;

            result = adin1110_SubmitTxBuffer(hDevice, &txBufDesc[txBufDescIdx]);
            if (result == ADI_ETH_SUCCESS)
            {
                txBufDescIdx++;
                if (txBufDescIdx >= BUFF_DESC_COUNT)
                {
                    txBufDescIdx = 0;
                }

                testFramesIdx++;
                if (testFramesIdx >= BUFF_DESC_COUNT)
                {
                    testFramesIdx = 0;
                }

                frameIdx++;
            }
            else
            {
                /* If Tx buffer submission fails (for example the Tx queue */
                /* may be full), then mark the buffer unavailable.  */
                txBufAvailable[testFramesIdx] = true;
            }
        }

        /* Report stats every ~2 seconds. */
        if (heartbeatTicks >= 8)
        {
            heartbeatTicks = 0;
            printStats(hDevice);
        }

    }

    /* Wait for queues to flush */
    while (rxIdx < FRAME_COUNT)
      ;

    while (rxIdx < txIdx)
      ;

    printStats(hDevice);

    result = adin1110_UnInit(hDevice);

}

void printStats(adin1110_DeviceHandle_t hDevice)
{
    adi_eth_Result_e            result = ADI_ETH_SUCCESS;
    adi_eth_MacStatCounters_t   stats;
    bool                        fail;

    result = adin1110_GetStatCounters(hDevice, &stats);
    DEBUG_RESULT("adin1110_GetStatCounters", result, ADI_ETH_SUCCESS);

    fail = false;
    fail = fail || (errorTxIdx || errorRxIdx);
    fail = fail || (stats.RX_CRC_ERR_CNT > 0);
    fail = fail || (stats.RX_ALGN_ERR_CNT > 0);
    fail = fail || (stats.RX_LS_ERR_CNT > 0);
    fail = fail || (stats.RX_PHY_ERR_CNT > 0);
    fail = fail || (stats.RX_DROP_FULL_CNT > 0);
    fail = fail || (stats.RX_DROP_FILT_CNT > 0);

    if (fail)
    {
        DEBUG_MESSAGE("Result: FAIL\r");
        DEBUG_MESSAGE("    Tx index errors: %d\r", errorTxIdx);
        DEBUG_MESSAGE("    Rx index errors: %d\r", errorRxIdx);
        BSP_ErrorLed(true);
    }
    else
    {
        DEBUG_MESSAGE("Result: PASS\r");
    }
    DEBUG_MESSAGE("Summary:\r");
    DEBUG_MESSAGE("     Sent frames:        %d\r", txIdx);
    DEBUG_MESSAGE("     Received frames:    %d\r", rxIdx);
    DEBUG_MESSAGE("     Statistics counters:\r");
    DEBUG_MESSAGE("         TX_FRM_CNT         = %d\r", stats.TX_FRM_CNT);
    DEBUG_MESSAGE("         TX_UCAST_CNT       = %d\r", stats.TX_UCAST_CNT);
    DEBUG_MESSAGE("         TX_MCAST_CNT       = %d\r", stats.TX_MCAST_CNT);
    DEBUG_MESSAGE("         TX_BCAST_CNT       = %d\r", stats.TX_BCAST_CNT);
    DEBUG_MESSAGE("         RX_FRM_CNT         = %d\r", stats.RX_FRM_CNT);
    DEBUG_MESSAGE("         RX_UCAST_CNT       = %d\r", stats.RX_UCAST_CNT);
    DEBUG_MESSAGE("         RX_MCAST_CNT       = %d\r", stats.RX_MCAST_CNT);
    DEBUG_MESSAGE("         RX_BCAST_CNT       = %d\r", stats.RX_BCAST_CNT);
    DEBUG_MESSAGE("         RX_CRC_ERR_CNT     = %d\r", stats.RX_CRC_ERR_CNT);
    DEBUG_MESSAGE("         RX_ALGN_ERR_CNT    = %d\r", stats.RX_ALGN_ERR_CNT);
    DEBUG_MESSAGE("         RX_LS_ERR_CNT      = %d\r", stats.RX_LS_ERR_CNT);
    DEBUG_MESSAGE("         RX_PHY_ERR_CNT     = %d\r", stats.RX_PHY_ERR_CNT);
    DEBUG_MESSAGE("         RX_DROP_FULL_CNT   = %d\r", stats.RX_DROP_FULL_CNT);
    DEBUG_MESSAGE("         RX_DROP_FILT_CNT   = %d\r", stats.RX_DROP_FILT_CNT);

}

